use glass_expand::attrs::{AttrType, DocsAttr};
use heck::ToPascalCase;
use moss::{
    from_meta::{FromExpr, FromMeta},
    syn_parse::SynParse,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
    Expr, FnArg, Ident, Item, Lit, LitStr, Path, Result, ReturnType, Signature, TraitItem,
    TraitItemFn, parse2, punctuated::Punctuated, token::Comma,
};

#[derive(SynParse)]
pub struct ApiModuleAttr {
    pub name: Option<LitStr>,
    pub path: Option<LitStr>,
    pub layers: Option<Layers>,
}

pub struct ApiModuleProps {
    pub name: Option<LitStr>,
    pub path: Option<LitStr>,
    pub layers: Layers,
}

impl From<ApiModuleAttr> for ApiModuleProps {
    fn from(value: ApiModuleAttr) -> Self {
        Self {
            name: value.name,
            path: value.path,
            layers: value.layers.unwrap_or_default(),
        }
    }
}

pub struct Layers {
    pub layer_idents_token: TokenStream,
}

impl Default for Layers {
    fn default() -> Self {
        Self {
            layer_idents_token: quote!(vec![]),
        }
    }
}

impl FromExpr for Layers {
    fn from_expr(expr: &syn::Expr) -> Result<Self> {
        if let Expr::Array(array) = expr {
            let mut layer_idents_token = quote!();
            for item in array.elems.iter() {
                if let Expr::Path(path) = item {
                    let path = &path.path;
                    let last_segment = path.segments.last();
                    if let Some(last_segment) = last_segment {
                        let segment_str = last_segment.ident.to_string();
                        layer_idents_token = quote! {
                            #layer_idents_token
                            #segment_str.into(),
                        };
                    }
                    continue;
                }
                return Err(moss::error::Error::unexpected_expr_type(item).into());
            }
            layer_idents_token = quote!(vec![#layer_idents_token]);
            return Ok(Self { layer_idents_token });
        }
        Err(moss::error::Error::unexpected_expr_type(expr).into())
    }
}

pub struct Tags(pub TokenStream);

impl FromExpr for Tags {
    fn from_expr(expr: &syn::Expr) -> Result<Self> {
        if let Expr::Array(array) = expr {
            let mut tags_token = quote!();
            for item in array.elems.iter() {
                tags_token = quote!(#tags_token #item.into(),);
            }
            tags_token = quote!(vec![#tags_token]);
            return Ok(Self(tags_token));
        }
        Err(moss::error::Error::unexpected_expr_type(expr).into())
    }
}

pub struct Protocol {
    pub token: TokenStream,
    pub kind: ProtocolKind,
    pub ty: TokenStream,
}

pub enum ProtocolKind {
    Http,
    Sse,
    Ws,
}

impl FromExpr for Protocol {
    fn from_expr(expr: &syn::Expr) -> Result<Self> {
        if let Expr::Lit(exprlit) = expr {
            let lit = &exprlit.lit;
            if let Lit::Str(lit) = lit {
                let lit = lit.value();
                if lit.eq_ignore_ascii_case("http") {
                    return Ok(Protocol {
                        token: quote!(apikit::Protocol::Http),
                        kind: ProtocolKind::Http,
                        ty: quote!(apikit::HttpProtocol),
                    });
                }
                if lit.eq_ignore_ascii_case("sse") {
                    return Ok(Protocol {
                        token: quote!(apikit::Protocol::Sse),
                        kind: ProtocolKind::Sse,
                        ty: quote!(apikit::SseProtocol),
                    });
                }
                if lit.eq_ignore_ascii_case("ws") {
                    return Ok(Protocol {
                        token: quote!(apikit::Protocol::Ws),
                        kind: ProtocolKind::Ws,
                        ty: quote!(apikit::WsProtocol),
                    });
                }
                return Err(moss::error::Error::unknown_value(&lit).into());
            }
        }
        Err(moss::error::Error::unexpected_expr_type(expr).into())
    }
}

impl Default for Protocol {
    fn default() -> Self {
        Self {
            token: quote!(apikit::Protocol::Http),
            kind: ProtocolKind::Http,
            ty: quote!(apikit::HttpProtocol),
        }
    }
}

#[derive(SynParse)]
pub struct ApiHandlerAttr {
    pub method: Option<Path>,
    pub name: Option<LitStr>,
    pub path: Option<LitStr>,
    pub tags: Option<Tags>,
    pub layers: Option<Layers>,
    pub protocol: Option<Protocol>,
}

impl From<ApiHandlerAttr> for ApiOperationProps {
    fn from(value: ApiHandlerAttr) -> Self {
        let method_token = value
            .method
            .map(|method| quote!(apikit::Method::#method))
            .unwrap_or(quote!(apikit::Method::POST));
        Self {
            name: value.name,
            path: value.path,
            tags: value.tags,
            layers: value.layers.unwrap_or_default(),
            method_token,
            protocol: value.protocol.unwrap_or_default(),
        }
    }
}

#[derive(FromMeta)]
#[strip]
pub enum ApiOperationAttr {
    #[name("get")]
    DefaultGet,
    Get {
        name: Option<LitStr>,
        path: Option<LitStr>,
        tags: Option<Tags>,
        layers: Option<Layers>,
        protocol: Option<Protocol>,
    },
    #[name("post")]
    DefaultPost,
    Post {
        name: Option<LitStr>,
        path: Option<LitStr>,
        tags: Option<Tags>,
        layers: Option<Layers>,
        protocol: Option<Protocol>,
    },
    #[name("put")]
    DefaultPut,
    Put {
        name: Option<LitStr>,
        path: Option<LitStr>,
        tags: Option<Tags>,
        layers: Option<Layers>,
        protocol: Option<Protocol>,
    },
    #[name("patch")]
    DefaultPatch,
    Patch {
        name: Option<LitStr>,
        path: Option<LitStr>,
        tags: Option<Tags>,
        layers: Option<Layers>,
        protocol: Option<Protocol>,
    },
    #[name("delete")]
    DefaultDelete,
    Delete {
        name: Option<LitStr>,
        path: Option<LitStr>,
        tags: Option<Tags>,
        layers: Option<Layers>,
        protocol: Option<Protocol>,
    },
    #[name("head")]
    DefaultHead,
    Head {
        name: Option<LitStr>,
        path: Option<LitStr>,
        tags: Option<Tags>,
        layers: Option<Layers>,
        protocol: Option<Protocol>,
    },
    #[name("options")]
    DefaultOptions,
    Options {
        name: Option<LitStr>,
        path: Option<LitStr>,
        tags: Option<Tags>,
        layers: Option<Layers>,
        protocol: Option<Protocol>,
    },
}

#[derive(Default)]
pub struct ApiOperationProps {
    pub name: Option<LitStr>,
    pub path: Option<LitStr>,
    pub tags: Option<Tags>,
    pub layers: Layers,
    pub method_token: TokenStream,
    pub protocol: Protocol,
}

impl From<ApiOperationAttr> for ApiOperationProps {
    fn from(value: ApiOperationAttr) -> Self {
        match value {
            ApiOperationAttr::Get {
                name,
                path,
                tags,
                layers,
                protocol,
            } => Self {
                name,
                path,
                tags,
                method_token: quote!(apikit::Method::GET),
                layers: layers.unwrap_or_default(),
                protocol: protocol.unwrap_or_default(),
            },
            ApiOperationAttr::Post {
                name,
                path,
                tags,
                layers,
                protocol,
            } => Self {
                name,
                path,
                tags,
                method_token: quote!(apikit::Method::POST),
                layers: layers.unwrap_or_default(),
                protocol: protocol.unwrap_or_default(),
            },
            ApiOperationAttr::Put {
                name,
                path,
                tags,
                layers,
                protocol,
            } => Self {
                name,
                path,
                tags,
                method_token: quote!(apikit::Method::PUT),
                layers: layers.unwrap_or_default(),
                protocol: protocol.unwrap_or_default(),
            },
            ApiOperationAttr::Patch {
                name,
                path,
                tags,
                layers,
                protocol,
            } => Self {
                name,
                path,
                tags,
                method_token: quote!(apikit::Method::PATCH),
                layers: layers.unwrap_or_default(),
                protocol: protocol.unwrap_or_default(),
            },
            ApiOperationAttr::Delete {
                name,
                path,
                tags,
                layers,
                protocol,
            } => Self {
                name,
                path,
                tags,
                method_token: quote!(apikit::Method::DELETE),
                layers: layers.unwrap_or_default(),
                protocol: protocol.unwrap_or_default(),
            },
            ApiOperationAttr::Head {
                name,
                path,
                tags,
                layers,
                protocol,
            } => Self {
                name,
                path,
                tags,
                method_token: quote!(apikit::Method::HEAD),
                layers: layers.unwrap_or_default(),
                protocol: protocol.unwrap_or_default(),
            },
            ApiOperationAttr::Options {
                name,
                path,
                tags,
                layers,
                protocol,
            } => Self {
                name,
                path,
                tags,
                method_token: quote!(apikit::Method::OPTIONS),
                layers: layers.unwrap_or_default(),
                protocol: protocol.unwrap_or_default(),
            },
            ApiOperationAttr::DefaultGet => Self {
                method_token: quote!(apikit::Method::GET),
                ..Default::default()
            },
            ApiOperationAttr::DefaultPost => Self {
                method_token: quote!(apikit::Method::POST),
                ..Default::default()
            },
            ApiOperationAttr::DefaultPut => Self {
                method_token: quote!(apikit::Method::PUT),
                ..Default::default()
            },
            ApiOperationAttr::DefaultPatch => Self {
                method_token: quote!(apikit::Method::PATCH),
                ..Default::default()
            },
            ApiOperationAttr::DefaultDelete => Self {
                method_token: quote!(apikit::Method::DELETE),
                ..Default::default()
            },
            ApiOperationAttr::DefaultHead => Self {
                method_token: quote!(apikit::Method::HEAD),
                ..Default::default()
            },
            ApiOperationAttr::DefaultOptions => Self {
                method_token: quote!(apikit::Method::OPTIONS),
                ..Default::default()
            },
        }
    }
}

pub fn generate_module_fn(
    module_ident_str: &str,
    submit_fn_ident: &Ident,
    module_props: &ApiModuleProps,
    docs_token: &TokenStream,
) -> Result<TokenStream> {
    let name_token = match &module_props.name {
        Some(name) => quote!(Some(#name.into())),
        None => quote!(None),
    };
    let path_token = match &module_props.path {
        Some(path) => quote!(Some(#path.into())),
        None => quote!(None),
    };
    let layers_ident_token = &module_props.layers.layer_idents_token;
    let expanded = quote! {
        fn #submit_fn_ident() -> apikit::doc::ApiModule {
            apikit::doc::ApiModule {
                ident: #module_ident_str.into(),
                name: #name_token,
                docs: #docs_token,
                position: module_path!().to_string(),
                path: #path_token,
                layers: #layers_ident_token,
            }
        }
        inventory::submit!(apikit::doc::ApiModuleSubmitItem {get_module_fn: #submit_fn_ident});
    };
    Ok(expanded)
}

pub struct FnInputs {
    pub ty_token: TokenStream,
    pub rpc_ty_token: TokenStream,
    pub rpc_arg_token: TokenStream,
    pub rpc_formal_token: TokenStream,
    pub ws_send_ty_token: TokenStream,
    pub ws_recv_ty_token: TokenStream,
}
impl FnInputs {
    pub fn from_inputs(is_ws: bool, fn_args: &Punctuated<FnArg, Comma>) -> Self {
        let mut ty_token = quote!();
        let mut rpc_ty_token = quote!();
        let mut rpc_arg_token = quote!();
        let mut rpc_formal_token = quote!();
        let mut is_first = true;
        let mut ws_send_ty_token = quote!();
        let mut ws_recv_ty_token = quote!();
        for (index, input) in fn_args.iter().enumerate() {
            if let syn::FnArg::Typed(pat_type) = input {
                let ty = &pat_type.ty;
                ty_token = quote! {
                    #ty_token #ty,
                };
                let param_ident = format_ident!("param{}", index);
                if is_ws && is_first {
                    ws_send_ty_token = quote! {<__TypeMapper as apikit::rpc::WebsocketParamSign<#ty>>::SendMessage};
                    ws_recv_ty_token = quote! {<__TypeMapper as apikit::rpc::WebsocketParamSign<#ty>>::ReceiveMessage};
                } else {
                    rpc_arg_token = quote! {
                        #rpc_arg_token #param_ident,
                    };
                    rpc_ty_token = quote! {
                        #rpc_ty_token <__TypeMapper as apikit::rpc::RequestParamSign<#ty>>::Input,
                    };
                    rpc_formal_token = quote! {
                        #rpc_formal_token #param_ident: <__TypeMapper as apikit::rpc::RequestParamSign<#ty>>::Input,
                    };
                }
                is_first = false;
            }
        }
        ty_token = quote!((#ty_token));
        rpc_ty_token = quote!((#rpc_ty_token));
        rpc_arg_token = quote!((#rpc_arg_token));
        Self {
            ty_token,
            rpc_ty_token,
            rpc_arg_token,
            rpc_formal_token,
            ws_recv_ty_token,
            ws_send_ty_token,
        }
    }

    pub fn extract_doc_param_token(&self, extract_token: TokenStream) -> TokenStream {
        let ty = &self.ty_token;
        quote! {
            #extract_token
            <__TypeMapper as apikit::doc::ApiRequestExtractor<#ty>>::extract(types, &mut request);
        }
    }
}

pub struct FnOutput {
    pub ty_token: TokenStream,
    pub rpc_ty_token: TokenStream,
}

impl FnOutput {
    pub fn from_output(output: &ReturnType) -> Self {
        match output {
            ReturnType::Default => Self {
                ty_token: quote!(()),
                rpc_ty_token: quote!(()),
            },
            ReturnType::Type(_, ty) => Self {
                ty_token: quote!(#ty),
                rpc_ty_token: quote!(<__TypeMapper as apikit::rpc::ResponseParamSign<#ty>>::Output),
            },
        }
    }

    pub fn extract_doc_param_token(&self, extract_token: TokenStream) -> TokenStream {
        let ty = &self.ty_token;
        quote! {
            #extract_token
            <__TypeMapper as apikit::doc::ApiResponseExtractor<#ty>>::extract(types, &mut response);
        }
    }
}

pub struct Operation {
    pub handle_def_ident: Ident,
    pub handle_def_generated: TokenStream,
    pub doc_generated: TokenStream,
    pub rpc_generated: TokenStream,
}

pub fn generate_operation_fn(
    module_ident_str: Option<&str>,
    submit_fn_ident: &Ident,
    module_props: &ApiModuleProps,
    operation_props: &ApiOperationProps,
    docs_token: &TokenStream,
    fn_sig: &Signature,
) -> Result<Operation> {
    let fn_ident = &fn_sig.ident;
    let ident_str = fn_sig.ident.to_string();
    let ApiOperationProps {
        name,
        layers,
        method_token,
        path,
        tags,
        protocol,
    } = operation_props;
    let name_token = match name {
        Some(name) => quote!(Some(#name.into())),
        None => quote!(None),
    };
    let parent_token = match &module_props.path {
        Some(path) => quote!(Some(#path)),
        None => quote!(None),
    };
    let path_token = match path {
        Some(path) => quote!(Some(#path)),
        None => quote!(None),
    };
    let tags_token = match tags {
        Some(tags) => &tags.0,
        None => &quote!(vec![]),
    };
    let mut extract_token = quote!();
    let is_ws = matches!(protocol.kind, ProtocolKind::Ws);
    let fn_inputs = FnInputs::from_inputs(is_ws, &fn_sig.inputs);
    let fn_output = FnOutput::from_output(&fn_sig.output);
    extract_token = fn_inputs.extract_doc_param_token(extract_token);
    extract_token = fn_output.extract_doc_param_token(extract_token);
    let fn_inputs_ty_token = &fn_inputs.ty_token;
    let fn_output_ty_token = &fn_output.ty_token;

    let layers_ident_token = &layers.layer_idents_token;
    let protocol_token = &protocol.token;
    let protocol_type_token = &protocol.ty;
    let handle_def_ident = get_handle_def_ident(module_ident_str.unwrap_or(""), ident_str.as_str());
    let handle_def_generated = quote! {
        pub struct #handle_def_ident;
        impl apikit::HandlerDef for #handle_def_ident {
            type Input = #fn_inputs_ty_token;

            type Output = #fn_output_ty_token;

            type Protocol = #protocol_type_token;

            fn method() -> apikit::Method {
                #method_token
            }

            fn path() -> &'static str {
                static PATH: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| {
                    <__Server as apikit::ServerDef>::route_path(#path_token, #parent_token, #ident_str, module_path!())
                });
                &PATH
            }

            fn protocol() -> apikit::Protocol {
                #protocol_token
            }
        }
    };

    let module_token = match module_ident_str {
        Some(module_ident_str) => quote!(Some(#module_ident_str.into())),
        None => quote!(None),
    };
    let doc_generated = quote! {

        #[cfg(feature = "doc")]
        fn #submit_fn_ident(types: &mut std::collections::HashMap<apikit::doc::ApiTypeId, apikit::doc::ApiTypeInfo>) -> apikit::doc::ApiOperation {
            let mut request = apikit::doc::ApiRequest::default();
            let mut response = apikit::doc::ApiResponse::default();

            #extract_token

            apikit::doc::ApiOperation {
                module: #module_token,
                position: module_path!().into(),
                ident: #ident_str.into(),
                name: #name_token,
                path: <#handle_def_ident as apikit::HandlerDef>::path().into(),
                docs: #docs_token,
                tags: #tags_token,
                method: #method_token.as_str().into(),
                protocol: #protocol_token.to_string(),
                request,
                response,
                layers: #layers_ident_token,
            }
        }

        #[cfg(feature = "doc")]
        inventory::submit!(apikit::doc::ApiOperationSubmitItem {get_operation_fn: #submit_fn_ident});
    };

    let rpc_arg_token = &fn_inputs.rpc_arg_token;
    let rpc_ty_token = &fn_inputs.rpc_ty_token;
    let rpc_formal_token = &fn_inputs.rpc_formal_token;
    let rpc_output_ty_token = &fn_output.rpc_ty_token;
    let rpc_ws_send_ty_token = &fn_inputs.ws_send_ty_token;
    let rpc_ws_recv_ty_token = &fn_inputs.ws_recv_ty_token;
    let rpc_generated = quote! {
        let addr = <S as apikit::rpc::ServerAddressManager>::get_server_addr(
            &self.server_manager,
            <__Server as apikit::ServerDef>::server_name(),
        );
        let path = <#handle_def_ident as apikit::HandlerDef>::path();
        let url = format!("{addr}{path}");
        let method = <#handle_def_ident as apikit::HandlerDef>::method();
    };
    let rpc_generated = match protocol.kind {
        ProtocolKind::Http => quote! {
            pub async fn #fn_ident(&self, #rpc_formal_token) -> Result<#rpc_output_ty_token, <C as apikit::rpc::http_client::HttpClient>::Error>
            where
                #rpc_output_ty_token: apikit::rpc::http_client::FromResponse<<C as apikit::rpc::http_client::HttpClient>::Response>,
                #rpc_ty_token: apikit::rpc::param::ParamAppender<<C as apikit::rpc::http_client::HttpClient>::ParamBuilder>,
            {
                #rpc_generated
                <C as apikit::rpc::http_client::HttpClient>::call_service(&self.client, &url, method, #rpc_arg_token).await
            }
        },
        ProtocolKind::Sse => quote! {
            pub async fn #fn_ident(&self, #rpc_formal_token) -> Result<apikit::rpc::sse_client::TypedEventSource<<C as apikit::rpc::sse_client::SseClient>::EventSource, #fn_output_ty_token>, <C as apikit::rpc::sse_client::SseClient>::Error>
            where
                <C as apikit::rpc::sse_client::SseClient>::EventSource: apikit::rpc::sse_client::EventSource<#fn_output_ty_token>,
                #rpc_ty_token: apikit::rpc::param::ParamAppender<<C as apikit::rpc::sse_client::SseClient>::ParamBuilder>,
            {
                #rpc_generated
                <C as apikit::rpc::sse_client::SseClient>::event_source(&self.client, &url, method, #rpc_arg_token).await
            }
        },
        ProtocolKind::Ws => quote! {
            pub async fn #fn_ident(&self, #rpc_formal_token) -> Result<apikit::rpc::ws_client::TypedWebsocket<<C as apikit::rpc::ws_client::WsClient>::Websocket, #rpc_ws_send_ty_token, #rpc_ws_recv_ty_token>, <C as apikit::rpc::ws_client::WsClient>::Error>
            where
                <C as apikit::rpc::ws_client::WsClient>::Websocket: apikit::rpc::ws_client::Websocket<#rpc_ws_send_ty_token, #rpc_ws_recv_ty_token>,
                #rpc_ty_token: apikit::rpc::param::ParamAppender<<C as apikit::rpc::ws_client::WsClient>::ParamBuilder>,
            {
                #rpc_generated
                <C as apikit::rpc::ws_client::WsClient>::websocket(&self.client, &url, method, #rpc_arg_token).await
            }
        },
    };

    Ok(Operation {
        handle_def_ident,
        handle_def_generated,
        doc_generated,
        rpc_generated,
    })
}

pub fn get_handle_def_ident(module_ident: &str, fn_ident: &str) -> Ident {
    format_ident!("{module_ident}{}Def_", fn_ident.to_pascal_case())
}

pub fn api_expand(module_attr: ApiModuleAttr, mut item: Item) -> Result<TokenStream> {
    let module_props = ApiModuleProps::from(module_attr);
    let mut expanded = quote!();
    match &mut item {
        Item::Trait(item_trait) => {
            let trait_ident = &item_trait.ident;
            let module_name = trait_ident.to_string();

            let client_ident = format_ident!("{module_name}Client");
            let mut clients_fn_generated = quote!();
            let mut docs_fns_generated = quote!();
            let mut handles_def_generated = quote!();

            let submit_fn_ident = format_ident!(
                "__submit_module_fn_{}",
                trait_ident.to_string().to_lowercase()
            );
            let item_attrs = &item_trait.attrs;
            let item_attrs = glass_expand::attrs::attrs_tokens_string(item_attrs, AttrType::Other);
            let module_expanded = generate_module_fn(
                &module_name,
                &submit_fn_ident,
                &module_props,
                &item_attrs.docs,
            )?;
            for fn_item in &mut item_trait.items {
                if let TraitItem::Fn(fn_item) = fn_item {
                    let mut operation_attr: Option<ApiOperationAttr> = None;
                    let mut docs_attr = DocsAttr::default();
                    for attr in &fn_item.attrs {
                        let result = ApiOperationAttr::try_from(attr);
                        if let Ok(attr) = result {
                            if operation_attr.is_some() {
                                return Err(moss::error::Error::custom(
                                    "duplicate operation attribute",
                                )
                                .into());
                            }
                            operation_attr = Some(attr);
                        } else {
                            let matched = docs_attr.visit_attr(attr);
                            if !matched {
                                return Err(moss::error::Error::custom("unknow attribute").into());
                            }
                        }
                    }
                    let operation_attr = match operation_attr {
                        Some(attr) => attr,
                        None => continue,
                    };
                    let operation_props: ApiOperationProps = operation_attr.into();
                    let docs_token = docs_attr.to_token_stream(true);
                    let submit_fn_ident = format_ident!(
                        "__submit_operation_fn_{}_{}",
                        module_name.to_lowercase(),
                        fn_item.sig.ident.to_string(),
                    );
                    fn_item.attrs = vec![];
                    let operation_expanded = generate_operation_fn(
                        Some(&module_name),
                        &submit_fn_ident,
                        &module_props,
                        &operation_props,
                        &docs_token,
                        &fn_item.sig,
                    )?;
                    let client_fn_token = operation_expanded.rpc_generated;
                    clients_fn_generated = quote! {
                        #clients_fn_generated
                        #client_fn_token
                    };

                    let doc_fn_token = operation_expanded.doc_generated;
                    docs_fns_generated = quote! {
                        #docs_fns_generated
                        #doc_fn_token
                    };

                    let handle_def_token = operation_expanded.handle_def_generated;
                    handles_def_generated = quote! {
                        #handles_def_generated
                        #handle_def_token
                    };
                }
            }
            let client_generated = client_generated(&client_ident, &clients_fn_generated);
            expanded = quote! {
                #expanded

                #module_expanded

                #docs_fns_generated
                #handles_def_generated
                #client_generated
            }
        }
        _ => return Err(moss::error::Error::custom("not supported item").into()),
    }

    let expanded = quote! {
        #expanded

        #item
    };
    Ok(expanded)
}

pub fn client_generated(ident: &Ident, body: &TokenStream) -> TokenStream {
    quote! {
        #[cfg(feature = "rpc")]
        pub struct #ident<C, S>
        where
            C: apikit::rpc::http_client::HttpClient,
            C: apikit::rpc::sse_client::SseClient,
            C: apikit::rpc::ws_client::WsClient,
            S: apikit::rpc::ServerAddressManager,
        {
            pub client: C,
            pub server_manager: S,
        }

        #[cfg(feature = "rpc")]
        impl<C, S> apikit::rpc::DutyClient<C, S> for #ident<C, S>
        where
            C: apikit::rpc::http_client::HttpClient,
            C: apikit::rpc::sse_client::SseClient,
            C: apikit::rpc::ws_client::WsClient,
            S: apikit::rpc::ServerAddressManager,
        {
            fn new(client: C, server_manager: S) -> Self {
                Self {
                    client,
                    server_manager,
                }
            }
        }

        #[cfg(feature = "rpc")]
        impl<C, S> #ident<C, S>
        where
            C: apikit::rpc::http_client::HttpClient,
            C: apikit::rpc::sse_client::SseClient,
            C: apikit::rpc::ws_client::WsClient,
            S: apikit::rpc::ServerAddressManager,
        {
            #body
        }
    }
}

pub fn handler_expand(module_attr: ApiHandlerAttr, item: Item) -> Result<TokenStream> {
    let operation_props = ApiOperationProps::from(module_attr);
    let mut expanded = quote!();
    match item {
        Item::Verbatim(fn_item) => {
            let fn_item: TraitItemFn = parse2(fn_item)?;
            let fn_name = fn_item.sig.ident.to_string();

            let client_ident = format_ident!("{}Client", fn_name.to_pascal_case());
            let mut clients_fn_generated = quote!();
            let mut docs_fns_generated = quote!();
            let mut handles_def_generated = quote!();

            let item_attrs = &fn_item.attrs;
            let item_attrs = glass_expand::attrs::attrs_tokens_string(item_attrs, AttrType::Other);

            let submit_fn_ident = format_ident!("__submit_operation_fn_{}", fn_name.to_lowercase());
            let operation_expanded = generate_operation_fn(
                None,
                &submit_fn_ident,
                &ApiModuleProps {
                    name: None,
                    path: None,
                    layers: Layers::default(),
                },
                &operation_props,
                &item_attrs.docs,
                &fn_item.sig,
            )?;
            let client_fn_token = operation_expanded.rpc_generated;
            clients_fn_generated = quote! {
                #clients_fn_generated
                #client_fn_token
            };

            let doc_fn_token = operation_expanded.doc_generated;
            docs_fns_generated = quote! {
                #docs_fns_generated
                #doc_fn_token
            };

            let handle_def_token = operation_expanded.handle_def_generated;
            handles_def_generated = quote! {
                #handles_def_generated
                #handle_def_token
            };
            let client_generated = client_generated(&client_ident, &clients_fn_generated);

            expanded = quote! {
                #expanded

                #docs_fns_generated
                #handles_def_generated
                #client_generated
            }
        }
        _ => return Err(moss::error::Error::custom("not supported item").into()),
    };
    Ok(expanded)
}

pub fn module_expand(module_attr: ApiModuleAttr, item: Item) -> Result<TokenStream> {
    match item {
        Item::Mod(item_mod) => {
            let module_ident_str = item_mod.ident.to_string();
            let submit_fn_ident = format_ident!("__submit_module_fn_{}", module_ident_str);
            let module_props = ApiModuleProps::from(module_attr);
            let item_attrs = &item_mod.attrs;
            let item_attrs = glass_expand::attrs::attrs_tokens_string(item_attrs, AttrType::Other);
            let expanded = generate_module_fn(
                module_ident_str.as_str(),
                &submit_fn_ident,
                &module_props,
                &item_attrs.docs,
            )?;
            Ok(expanded)
        }
        _ => Err(moss::error::Error::custom("not supported item").into()),
    }
}
